package com.azh.server.common;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zhaozhenhang <zhaozhenhang@kuaishou.com>
 * Created on 2022-04-25
 * @date  AbstractQuoteCache 实现了一个引用计数实现垃圾淘汰的缓存策略
 */
public abstract class AbstractQuoteCache<T> {

    // 实际缓存的数据 todo 后续会替换成自己的cache 实现
    private Map<Long, T> cache;

    // 资源的引用个数
    private Map<Long, Integer> references;

    // 正在被 获取的资源，通过key判断是否可以被获取..
    private Map<Long, Boolean> getting;

    private Lock lock;
    private int count = 0;
    private int maxResource;

    public AbstractQuoteCache(int maxResource) {
        this.maxResource = maxResource;
        cache = new HashMap<>();
        references = new HashMap<>();
        getting = new HashMap<>();
        lock = new ReentrantLock();
    }

    /**
     * 通过get方法从cache中获取资源
     * @param key
     * @return
     * @throws Exception
     */
    public T get(long key) throws Exception {
        for (; ; ) {
            lock.lock();
            if (getting.containsKey(key)) {
                // 请求的key 正在被其他线程获取
                lock.unlock();
                Thread.sleep(10);
                continue;
            }
            if (cache.containsKey(key)) {
                T obj = cache.get(key);
                references.put(key, references.get(key) + 1);
                lock.unlock();
                return obj;
            }
            if (maxResource > 0 && count >= maxResource) {
                lock.unlock();
                throw new Exception("cache 满了");
            }
            count++;
            getting.put(key, true);
            lock.unlock();
            break;
        }
        T obj = null;
        try {
            obj = getForDB(key);
        } catch (Exception e) {
            // todo 重复上锁，后续考虑其他方案
            lock.lock();
            count--;
            getting.remove(key);
            throw e;
        }
        lock.lock();
        getting.remove(key);
        cache.put(key, obj);
        references.put(key, 1);
        return obj;
    }

    public void release(long key) {
        lock.lock();
        try {
            int ref = references.get(key) - 1;
            if (ref == 0) {
                T obj = cache.get(key);
                // 资源驱逐时回db
                releaseForDB(obj);
                references.remove(key);
                cache.remove(key);
                count--;
            }else {
                references.put(key, ref);
            }
        }finally {
            lock.unlock();
        }
    }

    public void close() {
        lock.lock();
        try {
            Set<Long> keys = cache.keySet();
            for (long key : keys) {
                release(key);
                references.remove(key);
                cache.remove(key);
            }
        }finally {
            lock.unlock();
        }
    }

    /**
     * 当前资源不在缓存时的获取行为
     * @param key
     * @return
     * @throws Exception
     */
    protected abstract T getForDB(long key) throws Exception;

    /**
     * 当前资源被驱逐时的回写行为
     * @param obj
     */
    protected abstract void releaseForDB(T obj);
}
